feat: Screen Wake Lock API on Page#24324
Conversation
Add a per-UI WakeLock facade reached through Page#getWakeLock(). It exposes request(), release(), and an active-state Signal<Boolean>, matching the conventions used for PageVisibility and Geolocation: - Dedicated facade class under com.vaadin.flow.component.page with a package-private constructor. - Client logic lives in flow-client/src/main/frontend/WakeLock.ts and is side-effect-imported from Flow.ts; nothing inlined into Java. - State travels back as a vaadin-wake-lock-change CustomEvent on document.body with an ACTIVE / RELEASED detail; the facade listens on the UI element and writes to a private ValueSignal. - Client transparently re-acquires the lock on visibilitychange so a single request() covers the lifetime of a view; release() clears the "want lock" flag and drops the current sentinel. - Failures (insecure context, unsupported browser, denied) log at DEBUG and leave the active signal in its false state.
| * | ||
| * @return the wake lock facade, never {@code null} | ||
| */ | ||
| public WakeLock getWakeLock() { |
There was a problem hiding this comment.
Other browser feature APIs have their own static methods instead of being accessed throug Page.
Should we make this consistent?
There was a problem hiding this comment.
WakeLock is the odd one out — it uses the older per-UI facade pattern (Page.getWakeLock()), modeled on Page/History.
What is already aligned
- vaadin-wake-lock-change DOM event prefix ✓
- Event-to-signal bridging through ui.getElement() listener ✓
- window.Vaadin.Flow.wakeLock.* namespace ✓
- WakeLock.ts imported from Flow.ts ✓
- Cached read-only signal wrapper ✓
- DEBUG-level executeJs error logging ✓
- allowInert() on the state-change listener ✓
Where it diverges
Aspect: Entry point
Geolocation / Clipboard: Geolocation.getPosition(...) static
WakeLock: page.getWakeLock().request() facade
Recommendation: Redesign as static: WakeLock.request() / WakeLock.release() / WakeLock.activeSignal(), each with a (UI ui) overload; store active state on
UIInternals; drop Page.getWakeLock().
────────────────────────────────────────
Aspect: Package
Geolocation / Clipboard: com.vaadin.flow.component.geolocation (own, @NullMarked)
WakeLock: com.vaadin.flow.component.page (legacy, not @NullMarked)
Recommendation: Move to com.vaadin.flow.component.wakelock with @NullMarked package-info.java.
────────────────────────────────────────
Aspect: Capability hint
Geolocation / Clipboard: availabilityHintSignal() — GeolocationAvailability enum, seeded via bootstrap v-ga param
WakeLock: None — activeSignal() silently stays false when unsupported
Recommendation: Add WakeLock.availabilitySignal() returning Signal (SUPPORTED / UNSUPPORTED / UNKNOWN); probe 'wakeLock' in navigator &&
window.isSecureContext in collectBrowserDetails and seed via a v-wla bootstrap param.
────────────────────────────────────────
Aspect: Test seam
Geolocation / Clipboard: GeolocationClient + GeolocationClientFactory Lookup SPI
WakeLock: None — WakeLockSignalTest only verifies the JS-invocation string
Recommendation: Optional. Useful for browserless ITs, but heavier-weight for binary on/off state. Skip unless we plan to test scenarios beyond the current
signal-wiring tests.
There was a problem hiding this comment.
Do you want me to push an update for this or will you do it?
There was a problem hiding this comment.
Please go ahead if you have time
| * {@link #activeSignal()} to react to the actual state. Calling | ||
| * {@code request()} when a lock is already held is a no-op. | ||
| */ | ||
| public void request() { |
There was a problem hiding this comment.
I wonder if there should be an override taking an error handler function as an argument.
Let's assume there's a "Lock" button on the page that calls this API; you click on it, but the lock request fails; however, there will be no feedback to the user. In the worst case, you might also have disabled some other components, and there's no way to recover other than refreshing the whole page.
Moves WakeLock from com.vaadin.flow.component.page to its own @NullMarked package and replaces the per-UI facade reached through Page.getWakeLock() with static request()/release()/activeSignal() methods, each with an explicit-UI overload for background-thread callers. Per-UI state lives on UIInternals; the DOM listener that bridges vaadin-wake-lock-change events is installed lazily on first use. Drops Page.getWakeLock(). Adds WakeLock.availabilitySignal() returning a Signal<WakeLockAvailability> (SUPPORTED / UNSUPPORTED / UNKNOWN). The client probes 'wakeLock' in navigator && window.isSecureContext during collectBrowserDetails and ships the result as v-wla; ExtendedClientDetails seeds the signal from the bootstrap payload. This brings the API shape in line with Clipboard and Geolocation, both of which are static utilities with own @NullMarked packages and bootstrap-seeded availability hints.
Adds WakeLock.request(SerializableConsumer<WakeLockError>) and a matching (consumer, UI) overload, so a "Keep screen on" button can surface a message and re-enable itself when the browser refuses the request instead of silently leaving the UI in a half-disabled state. The client return type is now a structured result discriminating 'granted' / 'deferred' (page hidden, will retry on visibilitychange) / 'error' (persistent failure). The server fail-fasts on UNSUPPORTED availability — when the bootstrap probe already established the API is unusable, the error consumer fires synchronously without paying a round-trip. NotAllowedError maps to NOT_ALLOWED; everything else folds into UNKNOWN. The no-handler request() overloads keep their existing fire-and-forget behavior — failures still log at DEBUG only. Addresses PR #24324 review comment from mcollovati.
|
| }); | ||
| } | ||
|
|
||
| private static void handleResult(UI ui, |
There was a problem hiding this comment.
I had a hard time realizing that this method is basically handling only the potential error and not missing some implementation 😄
Could we rename the method to something like handleResultError?



Add a per-UI WakeLock facade reached through Page#getWakeLock(). It exposes request(), release(), and an active-state Signal, matching the conventions used for PageVisibility and Geolocation: